package org.chipu.jnids.sce.profile;

import java.util.ArrayList;
import java.util.Collection;

public class Stats {
	public enum Characteristic {
		AVERAGE, STANDARDDEVIATION, SKEWNESS, KURTOSIS
	}
	/** Number of data from which the other parameters have been calculated */
	public final int dataCount;
	public final float average;
	public final float standardDeviation;
	public final float skewness;
	public final float kurtosis;

	public Stats(int datacount, float average, float standardDeviation, float skewness, float kurtosis) {
		dataCount = datacount;
		this.average = average;
		this.standardDeviation = standardDeviation;
		this.skewness = skewness;
		this.kurtosis = kurtosis;
	}

	public static Stats getStats(ArrayList<Integer> ns) {
		int[] i = new int[ns.size()];
		for(int n = 0; n < ns.size(); n++)
			i[n] = ns.get(n);
		return getStats(i);
	}

	/**
	 * <ul>
	 * <li>variance: sum((xi-avg(x))^2*pi)/sum(pi) where p is the probability mass function (a constant in our
	 * case: pi=1) -> sum((xi-avg(x))^2)/N //TODOLATER try to put the varianza in function of mu2
	 * <li>muk=sum((xi-sum(xi*pi)^k)*pi) -> sum((xi-sum(xi)^k))/N
	 * <li>Skewness=mu3/desviacion_estandar^3
	 * <li>kurtosis=mu4/desviacion_estandar^4 - 3
	 * </ul>
	 * @see <a href="http://en.wikipedia.org/wiki/Summary_statistics">
	 *      http://en.wikipedia.org/wiki/Summary_statistics</a>
	 */
	public static Stats getStats(int... ns) {
		// SEE the last item may be 0
		// slightly faster than with classic for loops
		if (ns.length == 0)
			return null;
		float total = 0;
		for(int element : ns)
			total += element;
		float average = total / ns.length;

		float sum = 0, sum2 = 0, sum3 = 0;
		for(int element : ns) {
			sum += Math.pow((element - average), 2);
			sum2 += element - Math.pow(total, 3);
			sum3 += element - Math.pow(total, 4);
		}
		float variance = sum / ns.length;
		// Conversion to float: tradeoff between exactitude and efficiency
		float standardDeviation = (float) Math.sqrt(variance);
		float skewness = (float) (sum2 / (ns.length * Math.pow(standardDeviation, 3)));
		float kurtosis = (float) (sum3 / (ns.length * Math.pow(standardDeviation, 4))) - 3;

		return new Stats(ns.length, average, standardDeviation, skewness, kurtosis);
	}

	public static Stats getStats(Collection<Float> ns) {
		// SEE the last item may be 0
		if (ns.isEmpty())
			return null;
		float total = 0;
		for(Float n : ns)
			total += n;
		float average = total / ns.size();

		float sum = 0, sum2 = 0, sum3 = 0;
		for(Float n : ns) {
			sum += Math.pow((n - average), 2);
			sum2 += n - Math.pow(total, 3);
			sum3 += n - Math.pow(total, 4);
		}
		float variance = sum / ns.size();
		// Conversion to float: tradeoff between exactitude and efficiency
		float standardDeviation = (float) Math.sqrt(variance);
		float skewness = (float) (sum2 / (ns.size() * Math.pow(standardDeviation, 3)));
		float kurtosis = (float) (sum3 / (ns.size() * Math.pow(standardDeviation, 4))) - 3;

		return new Stats(ns.size(), average, standardDeviation, skewness, kurtosis);
	}

	public boolean isEmpty() {
		return average == 0 && standardDeviation == 0;
	}

	public float getValue(Characteristic c) {
		switch(c) {
		case AVERAGE:
			return average;
		case STANDARDDEVIATION:
			return standardDeviation;
		case SKEWNESS:
			return skewness;
		case KURTOSIS:
			return kurtosis;
		default:
			throw new IllegalArgumentException("Unrecognized: " + c);
		}
	}

	@Override
	public String toString() {
		if (isEmpty())
			return "";
		return "Stats[count=" + dataCount + ",average=" + average + ",standardDeviation=" + standardDeviation
			+ (Float.isInfinite(skewness) || Float.isNaN(skewness)? "": ",skewness=" + skewness)
			+ (Float.isInfinite(kurtosis) || Float.isNaN(kurtosis)? "": ",kurtosis=" + kurtosis) + "]";
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || !obj.getClass().isAssignableFrom(getClass()))
			return false;
		Stats o = (Stats) obj;
		return dataCount == o.dataCount && average == o.average && standardDeviation == o.standardDeviation
			&& skewness == o.skewness && kurtosis == o.kurtosis;
	}

	public String encode() {
		return "<stat count=\"" + dataCount + "\" average=\"" + average + "\" standardDeviation=\""
			+ standardDeviation + "\" skewness=\"" + skewness + "\" kurtosis=\"" + kurtosis + "\" />";
	}

	public static Stats decode(String s) {
		assert s.startsWith("<stat ");
		Stats rta = new Stats(StatType.getIntValue(s, "count"), Float.parseFloat(StatType.getValue(s,
			"average")), Float.parseFloat(StatType.getValue(s, "standardDeviation")), Float
			.parseFloat(StatType.getValue(s, "skewness")), Float.parseFloat(StatType.getValue(s, "kurtosis")));
		return rta;
	}
}